﻿using EasyLinkerSDK.Client.Interface;
using EasyLinkerSDK.Client.Message;
using EasyLinkerSDK.Common.Json;
using EasyLinkerSDK.Common.Tools;
using EasyLinkerSDK.Enum;
using EasyLinkerSDK.Model.Client.Common.Interface;
using EasyLinkerSDK.Model.Client.Msg;
using EasyLinkerSDK.Model.Client.Query;
using EasyLinkerSDK.Model.Server.Common.Interface;
using EasyLinkerSDK.Model.Server.Common.ServerError;
using EasyLinkerSDK.Model.Server.Common.ServerMessage;
using EasyLinkerSDK.Model.Server.Msg;
using EasyLinkerSDK.Model.Server.Query;
using EasyLinkerSDK.Model.User;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Protocol;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static EasyLinkerSDK.Client.Interface.EventHandler;

namespace EasyLinkerSDK.Client.MQTT
{
    public class MQTTClient : IELClient
    {
        private ClientInfo _clientInfo = null;

        private IMqttClient _mqttClient = null;

        private bool _isDoingReconnect = false;

        /// <summary>
        /// 消息到达委托
        /// </summary>
        /// <param name="message"></param>
        public event MessageReceivedEventHandler DoMessageReceived;

        /// <summary>
        /// 日志委托
        /// </summary>
        public event ClientLogEventHandler DoClientLog;

        public bool IsConnected
        {
            get
            {
                if (_mqttClient == null)
                {
                    return false;
                }
                else
                {
                    return _mqttClient.IsConnected;
                }
            }
        }

        public MQTTClient(ClientInfo clientInfo)
        {
            if (clientInfo == null
                || string.IsNullOrEmpty(clientInfo.DeviceId)
                || string.IsNullOrEmpty(clientInfo.UserId)
                || string.IsNullOrEmpty(clientInfo.Key)
                || string.IsNullOrEmpty(clientInfo.Server))
            {
                throw new NullReferenceException("Param can not null");
            }
            _clientInfo = clientInfo;
            _mqttClient = new MqttFactory().CreateMqttClient();
            _mqttClient.Connected += Connected;
            _mqttClient.Disconnected += Disconnected;
            _mqttClient.ApplicationMessageReceived += ApplicationMessageReceived;
        }

        public async Task<bool> Start()
        {
            try
            {
                var options = new MqttClientOptionsBuilder()
                    .WithClientId(_clientInfo.DeviceId)
                    .WithTcpServer(_clientInfo.Server, 8885)
                    .WithCredentials(_clientInfo.UserId, _clientInfo.Key)
                    .Build();
                await _mqttClient.ConnectAsync(options);
                return true;
            }
            catch (Exception ex)
            {
                DoClientLog?.Invoke("Connect error.", ex);
                return false;
            }
        }

        public async Task<bool> Stop()
        {
            try
            {
                if(_mqttClient == null || !_mqttClient.IsConnected)
                {
                    return true;
                }
                await Task.Run(() => _mqttClient.Dispose());
                return true;
            }
            catch (Exception ex)
            {
                DoClientLog?.Invoke("Disconnect error.", ex);
                return false;
            }
        }

        public async void Send(IClientMessage clientMsg)
        {
            if (clientMsg == null)
            {
                return;
            }
            string topic = string.Empty;
            switch (clientMsg.Type)
            {
                case MessageType.Control:
                case MessageType.Login:
                case MessageType.Error:
                    topic = "/client/control";
                    break;
                case MessageType.Msg:
                    topic = "/client/msg";
                    break;
                case MessageType.Query:
                    topic = "/client/query";
                    break;
                case MessageType.Heart:
                    topic = "/client/heart";
                    break;
                default:
                    DoClientLog?.Invoke("Unsupport client message type.", null);
                    break;
            }

            if (_mqttClient != null)
            {
                var message = new MqttApplicationMessageBuilder()
                    .WithTopic(topic)
                    .WithPayload(JsonHelper.SerializeObject(clientMsg))
                    .WithAtMostOnceQoS()
                    .WithRetainFlag();
                await _mqttClient.PublishAsync(message.Build());
            }
        }

        private void ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
        {
            string payload = e.ApplicationMessage.ConvertPayloadToString();
            if (string.IsNullOrEmpty(payload))
            {
                return;
            }
            try
            {
                ServerMessage message = JsonHelper.DeserializeJsonToObject<ServerMessage>(payload);
                if (message == null)
                {
                    return;
                }
                MessageProcessor processor = new MessageProcessor();
                IServerMessage iserver = null;
                switch (message.Type)
                {
                    case MessageType.Control:
                        if(message.Data == null)
                        {
                            return;
                        }
                        message.Data.Content = payload;
                        iserver = processor.BuildControlMessage(message);
                        if (iserver != null)
                        {
                            DoMessageReceived?.Invoke(iserver);
                        }
                        break;
                    case MessageType.Error:
                    case MessageType.Success:
                        ServerError serverError = JsonHelper.DeserializeJsonToObject<ServerError>(payload);
                        if (serverError != null)
                        {
                            DoMessageReceived?.Invoke(serverError);
                        }
                        break;
                    case MessageType.Heart:
                        Msg heartMsg = new Msg()
                        {
                            Device = DeviceType.other,
                            Uid = _clientInfo.UserId,
                            Key = _clientInfo.Key,
                            ProtocolType = ProtocolType.Mqtt,
                            Type = MessageType.Heart,
                            Action = ActionName.Other,
                            MessageId = Guid.NewGuid().ToString("N"),
                            DeviceId = _clientInfo.DeviceId,
                            Time = Time.Timestamp(),
                            Data = null,
                        };
                        Send(heartMsg);
                        break;
                    case MessageType.Msg:
                        MsgResult msg = JsonHelper.DeserializeJsonToObject<MsgResult>(payload);
                        if (msg != null)
                        {
                            DoMessageReceived?.Invoke(msg);
                        }
                        break;
                    case MessageType.Query:
                        Model.Server.Query.QueryResult query = JsonHelper.DeserializeJsonToObject<Model.Server.Query.QueryResult>(payload);
                        if (query != null)
                        {
                            DoMessageReceived?.Invoke(query);
                        }
                        break;
                    default:
                        break;
                }
            }
            catch(Exception ex)
            {
                DoClientLog?.Invoke("Application message received error.", ex);
                return;
            }
        }

        private void Disconnected(object sender, MqttClientDisconnectedEventArgs e)
        {
            if (!_clientInfo.IsReconnect)
            {
                DoClientLog?.Invoke("Client mqtt disconnect from server.", null);
                return;
            }
            if (_isDoingReconnect)
            {
                return;
            }
            Task.Run(async () =>
            {
                _isDoingReconnect = true;
                bool connectState = false;
                while (!connectState)
                {
                    DoClientLog?.Invoke("Retry to connect server.", null);
                    connectState = await Start();
                    if (connectState)
                    {
                        DoClientLog?.Invoke("Success to reconnect server.", null);
                        break;
                    }
                    Thread.Sleep(10000);  //每10s重连一次
                }
            });
        }

        private void Connected(object sender, MqttClientConnectedEventArgs e)
        {
            //设置正在重连状态
            _isDoingReconnect = false;
            //订阅
            Subscribe();

            DoClientLog?.Invoke("Topic subscribed.", null);
        }

        private async void Subscribe()
        {
            if (!_mqttClient.IsConnected)
            {
                DoClientLog("Client disconnected!",null);
                return;
            }
            try
            {
                TopicFilter[] topicFilters = new TopicFilter[] {
                    new TopicFilter($"/server/control/{_clientInfo.DeviceId}/{_clientInfo.Key}/#", MqttQualityOfServiceLevel.AtMostOnce),
                    new TopicFilter($"/server/query/{_clientInfo.DeviceId}/{_clientInfo.Key}/#", MqttQualityOfServiceLevel.AtMostOnce),
                    new TopicFilter($"/server/msg/{_clientInfo.DeviceId}/{_clientInfo.Key}/#", MqttQualityOfServiceLevel.AtMostOnce),
                    new TopicFilter($"/server/heart", MqttQualityOfServiceLevel.AtLeastOnce),
            };
                await _mqttClient.SubscribeAsync(topicFilters);
            }
            catch(Exception ex)
            {
                DoClientLog?.Invoke("Subscribe error.", ex);
            }
        }
    }
}
